﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using System.IO;
using System.Xml;

using Image = System.Drawing.Image;
using Bitmap = System.Drawing.Bitmap;
using Graphics = System.Drawing.Graphics;
using Rect = System.Drawing.Rectangle;
using GraphicsUnit = System.Drawing.GraphicsUnit;

using TileMapEditor.TileEngine;

namespace TileMapEditor
{
    public partial class Form1 : Form
    {
        SpriteBatch spriteBatch;
        Tileset tileset = null;
        Texture2D texture;
        Image tilesetBitmap;
        Texture2D cursor;
        TileMap tileMap = null;
        Camera camera = new Camera();
        FrmNewMap frmNewMap = new FrmNewMap();
        bool isChanged = false;

        int lastTick = Environment.TickCount;
        int mouseX;
        int mouseY;
        bool isMouseDown = false;
        bool trackMouse = false;

        Vector2 position = new Vector2();
        TileMapLayer currentLayer = null;

        public GraphicsDevice GraphicsDevice
        {
            get { return tileDisplay1.GraphicsDevice; }
        }

        public Form1()
        {
            InitializeComponent();
            layerToolStripMenuItem.Enabled = false;
            tileDisplay1.OnInitialize +=
            new EventHandler(tileDisplay1_OnInitialize);
            tileDisplay1.OnDraw +=
            new EventHandler(tileDisplay1_OnDraw);
            newMapToolStripMenuItem.Click +=
            new EventHandler(newMapToolStripMenuItem_Click);
            saveMapToolStripMenuItem.Enabled = false;
            saveMapToolStripMenuItem.Click +=
            new EventHandler(saveMapToolStripMenuItem_Click);
            openMapToolStripMenuItem.Click +=
            new EventHandler(openMapToolStripMenuItem_Click);
            exitToolStripMenuItem.Click +=
            new EventHandler(exitToolStripMenuItem_Click);
            newLayerToolStripMenuItem.Click +=
            new EventHandler(newLayerToolStripMenuItem_Click);
            clbLayers.SelectedIndexChanged +=
            new EventHandler(clbLayers_SelectedIndexChanged);
            nudCurrentTile.ValueChanged +=
            new EventHandler(nudCurrentTile_ValueChanged);
            nudCurrentTile.InterceptArrowKeys = false;
            Application.Idle += new EventHandler(Application_Idle);
            this.FormClosing +=
            new FormClosingEventHandler(Form1_FormClosing);
            tileDisplay1.MouseMove +=
            new MouseEventHandler(tileDisplay1_MouseMove);
            tileDisplay1.MouseDown +=
            new MouseEventHandler(tileDisplay1_MouseDown);
            tileDisplay1.MouseUp +=
            new MouseEventHandler(tileDisplay1_MouseUp);
            tileDisplay1.MouseEnter +=
            new EventHandler(tileDisplay1_MouseEnter);
            tileDisplay1.MouseLeave +=
            new EventHandler(tileDisplay1_MouseLeave);
        
        }

        void tileDisplay1_MouseMove(object sender, MouseEventArgs e)
        {
            mouseX = e.X;
            mouseY = e.Y;
        }

        void tileDisplay1_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
                isMouseDown = true;
        }

        void tileDisplay1_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
                isMouseDown = false;
        }

        void tileDisplay1_MouseEnter(object sender, EventArgs e)
        {
            trackMouse = true;
        }

        void tileDisplay1_MouseLeave(object sender, EventArgs e)
        {
            trackMouse = false;
        }

        void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            e.Cancel = false;
            if (isChanged)
            {
                DialogResult result = MessageBox.Show(
                "The map has changed. Are you sure you want to close?",
                "Save map?",
                MessageBoxButtons.YesNoCancel,
                MessageBoxIcon.Question);
                if (result == DialogResult.Yes)
                    e.Cancel = false;
                else
                    e.Cancel = true;
            }
        }

        void newLayerToolStripMenuItem_Click(object sender, EventArgs e)
        {
            FrmNewLayer frmNewLayer = new FrmNewLayer();
            frmNewLayer.ShowDialog();
            if (frmNewLayer.OKPressed)
            {
                TileMapLayer layer = new TileMapLayer(
                tileMap.MapWidth,
                tileMap.MapHeight);
                for (int x = 0; x < tileMap.MapWidth; x++)
                    for (int y = 0; y < tileMap.MapHeight; y++)
                        layer.SetTile(x, y, -1);
                tileMap.layers.Add(layer);
                currentLayer = tileMap.layers[tileMap.layers.Count - 1];
                clbLayers.Items.Add(frmNewLayer.LayerName, true);
                clbLayers.SelectedIndex = clbLayers.Items.Count - 1;
                isChanged = true;
            }
        }

        void clbLayers_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (clbLayers.Items.Count != 0)
            {
                currentLayer = tileMap.layers[clbLayers.SelectedIndex];
            }
        }

        void Application_Idle(object sender, EventArgs e)
        {
            tileDisplay1.Invalidate();
        }

        void tileDisplay1_OnInitialize(object sender, EventArgs e)
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            cursor = Texture2D.FromFile(GraphicsDevice, @"Content\cursor.png");
        }

        void tileDisplay1_OnDraw(object sender, EventArgs e)
        {
            if (Environment.TickCount - lastTick < 50)
                return;
            lastTick = Environment.TickCount;
            Logic();
            Render();
        }

        private void Render()
        {
            GraphicsDevice.Clear(Color.Black);
            if (tileMap != null)
            {
                for (int i = 0; i <= clbLayers.SelectedIndex; i++)
                {
                    if (clbLayers.GetItemCheckState(i) == CheckState.Checked)
                        DrawLayer(i);
                }
                DrawDisplay();
            }
        }

        private void DrawLayer(int layer)
        {
            int tile;
            Rectangle tileRect = new Rectangle(
            0,
            0,
            Engine.TileWidth,
            Engine.TileHeight);
            spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
            for (int y = 0; y < tileMap.MapHeight; y++)
                for (int x = 0; x < tileMap.MapWidth; x++)
                {
                    tile = tileMap.layers[layer].GetTile(x, y);
                    if (tile != -1)
                    {
                        tileRect.X = x * Engine.TileWidth
                        - (int)camera.Position.X;
                        tileRect.Y = y * Engine.TileHeight
                        - (int)camera.Position.Y;
                        spriteBatch.Draw(texture,
                        tileRect,
                        tileset.tiles[tile],
                        Color.White);
                    }
                }
            spriteBatch.End();
        }

        private void DrawDisplay()
        {
            spriteBatch.Begin();
            for (int x = 0; x < tileDisplay1.Width / Engine.TileWidth; x++)
                for (int y = 0; y < tileDisplay1.Height / Engine.TileHeight; y++)
                    spriteBatch.Draw(cursor,
                    new Rectangle(x * Engine.TileWidth,
                    y * Engine.TileHeight,
                    Engine.TileWidth,
                    Engine.TileHeight),
                    Color.White);
            spriteBatch.End();
            spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
            if (!rbErase.Checked)
            {
                spriteBatch.Draw(texture,
                new Rectangle(
                (int)(position.X / Engine.TileWidth) * Engine.TileWidth
                - (int)camera.Position.X,
                (int)(position.Y / Engine.TileHeight) * Engine.TileHeight
                - (int)camera.Position.Y,
                Engine.TileWidth,
                Engine.TileHeight),
                tileset.tiles[(int)nudCurrentTile.Value],
                Color.White);
            }
            spriteBatch.Draw(cursor,
            new Rectangle(
            (int)(position.X / Engine.TileWidth) * Engine.TileWidth
            - (int)camera.Position.X,
            (int)(position.Y / Engine.TileHeight) * Engine.TileHeight
            - (int)camera.Position.Y,
            Engine.TileWidth,
            Engine.TileHeight),
            Color.Red);
            spriteBatch.End();
        }

        void newMapToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (isChanged)
            {
                DialogResult result = MessageBox.Show(
                "The map has changed. Are you sure you want to continue?",
                "Save map?",
                MessageBoxButtons.YesNo,
                MessageBoxIcon.Question);
                if (result == DialogResult.No)
                    return;
            }
            frmNewMap.ShowDialog();
            if (frmNewMap.OkClicked)
            {
                tileset = frmNewMap.tileset;
                texture = Texture2D.FromFile(GraphicsDevice,
                frmNewMap.TilesetTextureName);
                tilesetBitmap = Bitmap.FromFile(frmNewMap.TilesetTextureName);
                tileMap = new TileMap(frmNewMap.MapWidth, frmNewMap.MapHeight);
                layerToolStripMenuItem.Enabled = true;
                saveMapToolStripMenuItem.Enabled = true;
                clbLayers.Items.Clear();
                clbLayers.Items.Add(frmNewMap.TileLayerName, true);
                clbLayers.SelectedIndex = 0;
                clbLayers.SelectionMode = SelectionMode.One;
                currentLayer = tileMap.layers[0];
                FillPictureBox(0);
                nudCurrentTile.Value = 0;
                nudCurrentTile.Maximum = tileset.tiles.Count - 1;
                this.Invalidate();
                isChanged = true;
            }
        }

        private void FillPictureBox(int index)
        {
            Image image = (Image)new Bitmap(128, 128);
            Rect destRectangle = new Rect(0, 0, 128, 128);
            Rect sourceRectangle = new Rect(
            tileset.tiles[index].X,
            tileset.tiles[index].Y,
            tileset.tiles[index].Width,
            tileset.tiles[index].Height);
            Graphics gi = Graphics.FromImage(image);
            gi.DrawImage(tilesetBitmap,
            destRectangle,
            sourceRectangle,
            GraphicsUnit.Pixel);
            pbCurrentTile.Image = image;
            this.Invalidate();
        }

        private void nudCurrentTile_ValueChanged(object sender, EventArgs e)
        {
            if (tileset != null)
            {
                int index = (int)nudCurrentTile.Value;
                FillPictureBox(index);
                tileDisplay1.Invalidate();
            }
        }

        void saveMapToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SaveFileDialog sfdSaveMap = new SaveFileDialog();
            sfdSaveMap.AddExtension = true;
            sfdSaveMap.CheckPathExists = true;
            sfdSaveMap.DefaultExt = "tmap";
            sfdSaveMap.Filter = "(Tile Maps *.tmap)|*.tmap";
            sfdSaveMap.ValidateNames = true;
            DialogResult result = sfdSaveMap.ShowDialog();
            if (result == DialogResult.OK)
            {
                try
                {
                    WriteMap(sfdSaveMap.FileName);
                    MessageBox.Show(
                    "Map saved successfully",
                    "Success",
                    MessageBoxButtons.OK);
                }
                catch
                {
                    MessageBox.Show(
                    "Error trying to save the map!",
                    "Error",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
                }
            }
        }

        private void WriteMap(string fileName)
        {
            XmlDocument xmlDoc = new XmlDocument();
            XmlElement root = xmlDoc.CreateElement("TileMap");
            xmlDoc.AppendChild(root);
            XmlAttribute attrib = xmlDoc.CreateAttribute("Width");
            attrib.Value = tileMap.MapWidth.ToString();
            root.Attributes.Append(attrib);
            attrib = xmlDoc.CreateAttribute("Height");
            attrib.Value = tileMap.MapHeight.ToString();
            root.Attributes.Append(attrib);
            XmlElement layerElement = xmlDoc.CreateElement("Layers");
            root.AppendChild(layerElement);
            foreach (TileMapLayer layer in tileMap.layers)
            {
                XmlElement element = xmlDoc.CreateElement("Layer");
                for (int y = 0; y < tileMap.MapHeight; y++)
                {
                    XmlElement row = xmlDoc.CreateElement("Row");
                    string innerText = "";
                    for (int x = 0; x < tileMap.MapWidth; x++)
                        innerText += layer.GetTile(x, y).ToString() + " ";
                    row.InnerText = innerText;
                    element.AppendChild(row);
                }
                layerElement.AppendChild(element);
            }
            xmlDoc.Save(fileName);
        }

        private void Logic()
        {
            if (tileMap != null)
            {
                if (trackMouse)
                {
                    if (mouseX <= Engine.TileWidth)
                        camera.Position.X -= Engine.TileWidth;
                    if (mouseX >= tileDisplay1.Width - Engine.TileWidth)
                        camera.Position.X += Engine.TileWidth;
                    if (mouseY <= Engine.TileHeight)
                        camera.Position.Y -= Engine.TileHeight;
                    if (mouseY >= tileDisplay1.Height - Engine.TileHeight)
                        camera.Position.Y += Engine.TileHeight;
                    camera.LockCamera();
                    position.X = mouseX + camera.Position.X;
                    position.Y = mouseY + camera.Position.Y;
                }
                int tileX = (int)position.X / Engine.TileWidth;
                int tileY = (int)position.Y / Engine.TileHeight;
                if (isMouseDown)
                {
                    isChanged = true;
                    if (rbDraw.Checked)
                    {
                        currentLayer.SetTile(
                        tileX,
                        tileY,
                        (int)nudCurrentTile.Value);
                    }
                    if (rbErase.Checked)
                    {
                        currentLayer.SetTile(
                        tileX,
                        tileY,
                        -1);
                    }
                }
                tbCursorLocation.Text = "( " + tileX.ToString() + ", ";
                tbCursorLocation.Text += tileY.ToString() + " )";
                tbCursorLocation.Invalidate();
            }
        }

        void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        void openMapToolStripMenuItem_Click(object sender, EventArgs e)
        {
            DialogResult saveChanges;
            if (isChanged)
            {
                saveChanges = MessageBox.Show(
                "The map has changed. Are you sure you want to continue?",
                "Save map?",
                MessageBoxButtons.YesNo,
                MessageBoxIcon.Question);
                if (saveChanges == DialogResult.No)
                    return;
            }
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.AddExtension = true;
            openFileDialog.CheckFileExists = true;
            openFileDialog.CheckPathExists = true;
            openFileDialog.Filter = "(Tile Sets *.tset)|*.tset";
            openFileDialog.Multiselect = false;
            openFileDialog.ValidateNames = true;
            openFileDialog.Title = "Select tile set to open";
            DialogResult result = openFileDialog.ShowDialog();
            if (result == DialogResult.OK)
            {
                try
                {
                    tileset = new Tileset(openFileDialog.FileName);
                    texture = Texture2D.FromFile(GraphicsDevice,
                    tileset.TilesetTextureName);
                    tilesetBitmap = Bitmap.FromFile(tileset.TilesetTextureName);
                }
                catch
                {
                    MessageBox.Show(
                    "Error reading in tile set!",
                    "Error",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
                    return;
                }
            }
            else
            {
                MessageBox.Show(
                "You must select a tile set to proceed!",
                "Error",
                MessageBoxButtons.OK,
                MessageBoxIcon.Error);
                return;
            }
            openFileDialog.Filter = "(Tile Maps *.tmap)|*.tmap";
            openFileDialog.Title = "Select map to open";
            result = openFileDialog.ShowDialog();
            if (result == DialogResult.OK)
            {
                try
                {
                    tileMap = new TileMap(openFileDialog.FileName);
                }
                catch
                {
                    MessageBox.Show(
                    "Error reading in tile map!",
                    "Error",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
                    return;
                }
            }
            clbLayers.Items.Clear();
            layerToolStripMenuItem.Enabled = true;
            saveMapToolStripMenuItem.Enabled = true;
            foreach (string layer in tileMap.layerNames)
                clbLayers.Items.Add(layer, true);
            clbLayers.SelectedIndex = clbLayers.Items.Count - 1;
            clbLayers.SelectionMode = SelectionMode.One;
            currentLayer = tileMap.layers[tileMap.layers.Count - 1];
            FillPictureBox(0);
            nudCurrentTile.Value = 0;
            nudCurrentTile.Maximum = tileset.tiles.Count - 1;
            this.Invalidate();
        }
    }
}
